/*
* ============================================================================
*  Name        : platform_sos.h
*  Part of     : Finite state machine utilities
*  Description : Platform abstruction layer
*  Version     : %version: %
*  
*
* ============================================================================
*/
/**
 * @file sea_platform_sos.h
 *
 * @ingroup Platforms
 */

#if !defined( __sea_platform_sos__ )
#define __sea_platform_sos__

#include <string>
#include <vector>
#include <map>
#include <typeinfo>
#include <algorithm>
#include <stdlib.h>
#include <assert.h>
#include <time.h>

#include "sea_assert.h"

#include <e32std.h> 
#include <e32base.h>
#include <e32debug.h>

namespace sea
{
    typedef std::string String;
    typedef TUint64 OsThreadHandle;

    #define strtok_s strtok_r
    #define strncpy_s( dest, dlen, src, slen ) strncpy( dest, src, slen )

    #define SEA_PRETTY_FUNCTION __FUNCTION__

    /**
     * @class OsThread
     * @brief Thread in Symbian
     *
     * @ingroup Platforms
     */
    class OsThread : public Object
    {
    public:
        OsThread( ) :
          m_threadId( 0 ),
          m_started( false )
        {
        }

        ~OsThread( ) 
        {
            if ( m_started )
            {
                m_thread.Kill( KErrNone );
                m_thread.Close( );
            }
        }

        virtual void exec( ) { }

        static int entry( void* param )
        {
            OsThread* the_thread = reinterpret_cast< OsThread* >( param );
            sea::ref< OsThread > thread = the_thread;
            CTrapCleanup* cleanup = CTrapCleanup::New( );
            CActiveScheduler* as = new ( ELeave ) CActiveScheduler;

            the_thread->exec( );
            the_thread->m_started = false;
            the_thread->m_threadId = 0;

            CActiveScheduler::Install( NULL );
            delete as;
            delete cleanup;

            return 0;
        }
        
        static OsThreadHandle currentThreadId( ){ return RThread( ).Id( ).Id( ); }

        void start( ) 
        { 
            if ( !m_started )
            {
                if ( m_thread.Create( KNullDesC, &OsThread::entry, 8 * 1024, NULL, this ) == KErrNone )
                {
                    m_threadId = m_thread.Id( ).Id( );
                    m_started = true;
                    m_thread.Resume( ); 
                }
            }
        }

        void wait( uint32 time = ( uint32 )-1 )
        {
            SEA_UNUSED( time );
            m_thread.Logon( m_status );
            User::WaitForRequest( m_status );
        }

        OsThreadHandle id( ){ return m_threadId; }

        void setPriority( OsThreadPriority value )
        {
            if ( value >= Lowest && value <= Highest )
            {
                m_thread.SetPriority( m_native_priority[ value ] );
            }
        }
        bool isRunning( ){ return m_started; }

    private:
        static TThreadPriority m_native_priority[ ];

        RThread m_thread;
        OsThreadHandle m_threadId;
        bool m_started;
        TRequestStatus m_status;
    };

    /**
     * @class OsSemaphore
     * @brief Semaphore in Symbian
     *
     * @ingroup Platforms
     */
    class OsSemaphore : public Object
    {
    public:
        OsSemaphore( int init_counter = 0 )
        {
            if( m_semaphore.CreateLocal( init_counter ) != KErrNone )
            {
                ASSERT_DEBUG( false );
            }
        }
        
        ~OsSemaphore( ) { m_semaphore.Close( ); }
        
        virtual void signal( int number = 1 ) 
        { 
            m_semaphore.Signal( number );
        }

        virtual void wait( int number = 1 ) 
        { 
            while( number-- )
            {
                m_semaphore.Wait( );
            }
        }
    private:
        RSemaphore m_semaphore;
    };

    /**
     * @class OsMutex
     * @brief Mutex in Symbian
     *
     * @ingroup Platforms
     */
    class OsMutex : public Object
    {
    public:
        OsMutex( ) : m_locked( false ) 
        { 
            if ( m_mutex.CreateLocal( ) != KErrNone )
            { 
                ASSERT_DEBUG( false ); 
            } 
        }
        ~OsMutex( ) { m_mutex.Close( ); }

        bool try_enter( )
        { 
            if ( m_locked.compare_set( 0, 1 ) != 0 )
            {
                return false;
            }
            
            enter( );
            return true;
        }

        void enter( )
        { 
            m_locked.set( 1 );
            m_mutex.Wait( );
        }

        void leave( )
        { 
            m_mutex.Signal( ); 
            m_locked.set( 0 );
        }

        /**
         * @class Scoped
         * @brief Mutex scoped lock/unlock
         *
         * @ingroup Platforms
         */
        class Scoped
        {
        public:
            Scoped( OsMutex* critical_section, bool do_try = false );
            ~Scoped( );
        private:
            OsMutex* m_mutex;
        };

    private:
        RMutex m_mutex;
        OsAtomic m_locked;
    };

    /**
     * @class OsSpinlock
     * @brief Spinlock in Symbian
     *
     * @ingroup Platforms
     */
    class OsSpinlock
    {
    public:
        OsSpinlock( ) : m_thread_id( ( OsThreadHandle )-1 ), m_value( 0 ) { }
        ~OsSpinlock( ) { }

        void enter( )
        {
            if ( m_thread_id == OsThread::currentThreadId( ) )
            {
                m_count++;
                return;
            }

            TUint32 expected = 0;

            while( !__e32_atomic_cas_rlx32( &m_value, &expected, 1 ) )
            {
                User::After( 1 );
            }

            if ( m_thread_id == ( OsThreadHandle )-1 )
            {
                m_thread_id = OsThread::currentThreadId( );
            }

            m_count++;

        }

        void leave( )
        {
            m_count--;

            if( m_count == 0 )
            {
                m_thread_id = ( OsThreadHandle )-1;
                __e32_atomic_swp_rlx32( &m_value, 0 );
            }
        }

        /**
         * @class Scoped
         * @brief Mutex scoped lock/unlock
         *
         * @ingroup Platforms
         */
        class Scoped
        {
        public:
            Scoped( OsSpinlock* critical_section ): m_lock( critical_section ) { if ( m_lock ) m_lock->enter( ); }
            ~Scoped( ){ if ( m_lock ) m_lock->leave( ); }
        private:
            OsSpinlock* m_lock;
        };
    private:
        TUint32 m_value;
        OsThreadHandle m_thread_id;
    };


    /**
     * @class OsSignal
     * @brief Signal in Symbian
     *
     * @ingroup Platforms
     */
    class OsSignal : public OsSemaphore
    {
    public:
        OsSignal( ) : OsSemaphore( 0 ) { }
        bool wait( int timeout )
        {
            OsSemaphore::wait( timeout ); 
            return true;
        }
        void wakeup( ){ OsSemaphore::signal( ); }
    };

    /**
     * @class OsTime
     * @brief Millisecond timer
     *
     * @ingroup Platforms
     */
    class OsTime
    {
    public:
        OsTime( ) { start( ); }

        /**
         * Start counting time
         */
        void start( ){ m_current.UniversalTime( ); }

        /**
         * Get elapsed time
         *
         * @return Time in milliseconds
         */
        int elapsed( ) 
        { 
            TInt64 result = TTime( ).MicroSecondsFrom( m_current ).Int64( );
            return result / 1000; 
        }

    private:
        TTime m_current;
    };

    /**
     * File open prototype
     * @brief substitude in Linux
     */
    int fopen_s( FILE** file, const char *file_name, const char *mode );

}

#endif
